﻿#region Copyright and License Notice
// Copyright (C)2010-2016 - Rob Levine and other contributors
// http://configgen.codeplex.com
// 
// This file is part of ConfigGen.
// 
// ConfigGen is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// ConfigGen is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License and 
// the GNU Lesser General Public License along with ConfigGen.  
// If not, see <http://www.gnu.org/licenses/>
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using ConfigGen.Core.TokenHandling;
using NUnit.Framework;

namespace ConfigGen.Core.Tests.TokenHandling
{
    /// <summary>
    /// The token locator tests.
    /// </summary>
    [TestFixture]
    public class TokenLocatorTests
    {
        private List<string> _locatedTokens;
        private bool _replaceTokenOnBeingLocated;

        [Test]
        public void LocateTokens_NullStartDelimiterThrows()
        {
            var tokenLocator = new TokenLocator();

            using (var reader = new StringReader("INPUT STRING"))
            using (var writer = new StringWriter())
            {
                Assert.Throws<ArgumentNullException>(() => tokenLocator.LocateTokens(reader, writer, null, "%]", (s) => s));
            }
        }

        [Test]
        public void LocateTokens_NullEndDelimiterThrows()
        {
            var tokenLocator = new TokenLocator();

            using (var reader = new StringReader("INPUT STRING"))
            using (var writer = new StringWriter())
            {
                Assert.Throws<ArgumentNullException>(() => tokenLocator.LocateTokens(reader, writer, "[%", null, (s) => s));
            }
        }

        [Test]
        public void LocateTokens001_SingleToken()
        {
            const string testData = "a [%test%] string";

            TestResult testResult = ExecuteLocateTokensAndReturnResult(testData, false);

            Assert.AreEqual(1, _locatedTokens.Count, "Incorrect number of tokens located");
            Assert.AreEqual("test", _locatedTokens[0], "Incorrect token located");
            Assert.AreEqual("a  string", testResult.Result, "Incorrect output");
        }

        [Test]
        public void LocateTokens002_NoTokenLocated()
        {
            const string testData = "This is a string";

            TestResult testResult = ExecuteLocateTokensAndReturnResult(testData, false);

            Assert.AreEqual(0, _locatedTokens.Count, "Incorrect number of tokens located");        
            Assert.AreEqual("This is a string", testResult.Result, "Incorrect output");
        }

        [Test]
        public void LocateTokens003_ReplacementValueIsInserted()
        {
            const string testData = "This is a [%test%] string";

            TestResult testResult = ExecuteLocateTokensAndReturnResult(testData, true);

            Assert.AreEqual(1, _locatedTokens.Count, "Incorrect number of tokens located");
            Assert.AreEqual("test", _locatedTokens[0], "Incorrect token located");
            Assert.AreEqual("This is a test-REPLACED string", testResult.Result, "Incorrect output");
        }

        [Test]
        public void LocateTokens004_UnexpectedStartToken()
        {
            const string testData = "This is a [%t[%est%] string";

            var exception = Assert.Throws<UnexpectedTokenDelimiterException>(() => ExecuteLocateTokensAndReturnResult(testData, false));

            Assert.IsTrue(exception.Message.Contains("start"), "Exception should have contained 'start' in message");
        }

        [Test]
        public void LocateTokens005_UnexpectedEndToken()
        {
            const string testData = "This is a [%test%] %] string";

            var exception = Assert.Throws<UnexpectedTokenDelimiterException>(() => ExecuteLocateTokensAndReturnResult(testData, false));

            Assert.IsTrue(exception.Message.Contains("end"), "Exception should have contained 'end' in message");
        }

        [Test]
        public void LocateTokens006_UnexpectedEndOfStream()
        {
            const string testData = "This is a [%test";

            Assert.Throws<EndOfStreamException>(() => ExecuteLocateTokensAndReturnResult(testData, false));
        }

        [Test]
        public void LocateTokens007_SameDelimiterForStartAndEnd()
        {
            const string testData = "a !!test!! string";

            _locatedTokens = new List<string>();

            var locator = new TokenLocator();

            using (var reader = new StringReader(testData))
            using (var writer = new StringWriter())
            {
                locator.LocateTokens(reader, writer, "!!", "!!", OnTokenLocatedCallback);

                string output = writer.GetStringBuilder().ToString();
                Assert.AreEqual(1, _locatedTokens.Count, "Incorrect number of tokens located");
                Assert.AreEqual("test", _locatedTokens[0], "Incorrect token located");
                Assert.AreEqual("a  string", output, "Incorrect output");
            }
        }

        [Test]
        public void LocateTokens008_TokenUseCounts_UsedToken()
        {
            const string testData = "a [%test%] string";

            TestResult testResult = ExecuteLocateTokensAndReturnResult(testData, false);
            var tokenCounts = testResult.TokenCounts;
            Assert.AreEqual(1, tokenCounts.TokenUseCounts.Count, "Incorrect TokenUseCounts.Count");
            Assert.IsTrue(tokenCounts.TokenUseCounts.ContainsKey("test"), "'test' expected in TokenUseCounts");
            Assert.AreEqual(1, tokenCounts.TokenUseCounts["test"], "Incorrect count for 'test'");

            Assert.AreEqual(0, tokenCounts.UnrecognisedTokenCounts.Count, "Incorrect UnrecognisedTokenCounts.Count");
        }

        [Test]
        public void LocateTokens009_TokenUseCounts_UnrecognisedToken()
        {
            const string testData = "a [%test%] string";

            var locator = new TokenLocator();

            using (var reader = new StringReader(testData))
            using (var writer = new StringWriter())
            {
                var tokenCounts = new TokenCounts();
                locator.TokenUsed += tokenCounts.IncrementTokenUseCount;
                locator.UnrecognisedToken += tokenCounts.IncrementUnrecognisedTokenCount;

                locator.LocateTokens(reader, writer, "[%", "%]", s => { throw new NoMatchingTokenException("No matching token"); });

                Assert.AreEqual(1, tokenCounts.UnrecognisedTokenCounts.Count, "Incorrect UnrecognisedTokenCounts.Count");
                Assert.IsTrue(tokenCounts.UnrecognisedTokenCounts.ContainsKey("test"), "'test' expected in UnrecognisedTokenCounts");
                Assert.AreEqual(1, tokenCounts.UnrecognisedTokenCounts["test"], "Incorrect count for 'test'");
                Assert.AreEqual(0, tokenCounts.TokenUseCounts.Count, "Incorrect TokenUseCounts.Count");
            }
        }

        [Test]
        public void LocateTokens010_AdjacentReplacementValuesAreInserted()
        {
            const string testData = "This is a [%test%][%again%] string";

            TestResult testResult = ExecuteLocateTokensAndReturnResult(testData, true);

            Assert.AreEqual(2, _locatedTokens.Count, "Incorrect number of tokens located");
            Assert.AreEqual("test", _locatedTokens[0], "Incorrect token located");
            Assert.AreEqual("again", _locatedTokens[1], "Incorrect token located");
            Assert.AreEqual("This is a test-REPLACEDagain-REPLACED string", testResult.Result, "Incorrect output");
        }

        private TestResult ExecuteLocateTokensAndReturnResult(string testInputString, bool replaceTokens)
        {
            _locatedTokens = new List<string>();
            _replaceTokenOnBeingLocated = replaceTokens;

            var locator = new TokenLocator();

            using (var reader = new StringReader(testInputString))
            using (var writer = new StringWriter())
            {
                var tokenCounts = new TokenCounts();
                locator.TokenUsed += tokenCounts.IncrementTokenUseCount;
                locator.UnrecognisedToken += tokenCounts.IncrementUnrecognisedTokenCount;
                locator.LocateTokens(reader, writer, "[%", "%]", OnTokenLocatedCallback);
                var result = writer.GetStringBuilder().ToString();

                return new TestResult {Result = result, TokenCounts = tokenCounts};
            }
        }

        public class TestResult
        {
            public string Result { get; set; }
            public TokenCounts TokenCounts { get; set; }
        }

        private string OnTokenLocatedCallback(string token)
        {
            _locatedTokens.Add(token);
            if (_replaceTokenOnBeingLocated)
            {
                return token + "-REPLACED";
            }

            return null;
        }
    }
}
